
#pragma once


#include <Arduino.h>

namespace hexpro {

    namespace hide {

        constexpr auto max(auto a, auto b) {
            return (a > b) ? a : b;
        }

        constexpr uint8_t half_byte_to_hex(uint8_t d) {
            return (d < 10) ? (d + '0') : (d + 'A' - 10);
        };

        constexpr uint8_t hex_to_half_byte(uint8_t d) {
            if (d < ':') {
                return d - '0';
            }
            else if (d > '@') {
                return d - 'A' + 10;
            }
            else {
                return 0xee;  // 随便返回一个大于0x0f 的无效数值
            }
        }

        struct two_hex {
            uint8_t l;
            uint8_t h;
        };

        constexpr two_hex byte_to_hex(uint8_t d) {
            return {.l = half_byte_to_hex(d & 0x0f), .h = half_byte_to_hex(d >> 4)};
        }


    }  // namespace hide


    using ValueType = uint32_t;


    constexpr uint8_t VALUE_WIDTH = sizeof(ValueType);
    constexpr size_t TX_VALUE_COUNT = 4;  // 发送帧最多可以附加几个数值
    constexpr size_t RX_VALUE_COUNT = 4;  // 接收帧最多能存储几个数值，多余的数值将被丢弃。从机可以返回NO 响应，表示数据过多。

    constexpr size_t MIN_VALUE_COUNT = 3;
    constexpr size_t MAX_VALUE_COUNT = hide::max(TX_VALUE_COUNT, RX_VALUE_COUNT);

    static_assert((TX_VALUE_COUNT >= MIN_VALUE_COUNT) && (RX_VALUE_COUNT >= MIN_VALUE_COUNT));

    constexpr size_t MIN_FRAME_LEN = 6;                                                    // 最小的帧长度，此时不包含值
    constexpr size_t MAX_TX_FRAME_LEN = MIN_FRAME_LEN + TX_VALUE_COUNT * VALUE_WIDTH * 2;  // 每个字节用两个ASCII 码表示，所以要乘二
    constexpr size_t MAX_RX_FRAME_LEN = MIN_FRAME_LEN + RX_VALUE_COUNT * VALUE_WIDTH * 2;
    constexpr size_t MAX_FRAME_LEN = hide::max(MAX_RX_FRAME_LEN, MAX_TX_FRAME_LEN);

    constexpr size_t TX_BUFFER_THRESHOLD = 10;  // 用待发送的长度减去TX BUFFER 的空闲空间，只要小于这个阈值，就可以开始发送。
                                                // 如果小于阈值，表示只有较少的数据无法一次写入tx buffer，
                                                // 对于使用环形缓冲区发送数据的底层驱动，向tx buffer 写入的同时，
                                                // 数据已经在发送了，所以写到最后几个字节时，可能缓冲区又有了空闲空间，
                                                // 实际效果还是非阻塞，或者只阻塞很短的时间。
    constexpr size_t FIXED_TX_FRAME_LEN = 0;    // 如果大于0，判断缓冲区空闲时，不实时计算帧长度，用这个固定值代替

    constexpr bool USE_SOFT_SERIAL = false;  // TODO: SoftSerial 不需要考虑TX BUFFER 的问题

    enum class error_level {
        none = 0,

        rx_timeout = 1,     // 读取超时
        no_proper_end = 2,  // 帧尾错误
        sum_mismatch = 3,   // 校验和不匹配
        value_error,       // 数据、校验码的格式或编码错误
        too_many_value,    // 值的数量过多，这个错误不会中止读取，还会接着把一帧读完，然后计算校验和。
                           // 如果校验和正确，从机至少应该响应NO。
        waiting,           // 正在等待从机响应，且并没有超时
        response_timeout,  // 从机响应超时
    };

    /**
     * @brief 协议处理器，负责数据帧的收发，并处理控制逻辑。使用时应实现它的子类，提供必要的回调成员函数。
     *
     * ## 协议格式：
     *
     * 模仿Modbus ASCII 协议，所有数据都会转换成ASCII 字符，再经过串口传输，每个数据字节转换成两个十六进制字符。
     *
     * 帧的一般格式分为四段：
     *
     * [# #] [ @ ] [ FF AA, BB 00 AA 20, CC, ] [ EE ] [ \r \n ]
     *
     * 1. 前两字节# # 是从机地址。从机地址也必须用ASCII 码表示，重复一遍作为校验；
     * 2. 第二段用一字节@ 表示帧类型；
     * 3. 第三段为若干个数据，每个数据都转换成十六进制字符串形式，数据格式为：Big-Endian、MSB 优先、右对齐、省略高位0；
     * 4. 数据值之间用逗号隔开，每个值默认最大不超过32 位，所以一组十六进制字符串最长为8 个字符；
     * 5. 第四段是校验和，将帧类型标识以及所有数据的二进制值按字节求和，得到的一字节结果转换为两个十六进制字符；
     * 6. 用\r\n 作为帧尾，两个字符能相互作为校验；
     *
     * ## 数据格式
     *
     * 每个值后必须跟一个逗号，如果值为0，可以省略值。比如，[ FF, , AA, ] 是三个值，中间第二个值为0，省略。
     * 16 进制字符的字母可以用大写，也可以小写。
     *
     * ## 标准响应帧
     *
     * 主机向从机发送数据时，如果帧数据校验失败，或数据不全，从机直接将帧丢弃。如果从机正确接收，必须返回响应。有三种预定义的标准响应：
     *
     * 1. OK    响应：[ # # ] [ ! ] [ 00, ] [ EE ] [ \r \n ]，从机正确处理了请求；
     * 2. RETRY 响应：[ # # ] [ ! ] [ 44, ] [ EE ] [ \r \n ]，从机暂时无法处理，主机应稍后重发；
     * 3. NO    响应：[ # # ] [ ! ] [ EE, ] [ EE ] [ \r \n ], 从机拒绝处理请求，可以附加错误信息，或要求主机查询错误；
     *
     * 每种标准响应都有一个固定前缀，如OK 响应的00；可以继续附加其他数据，帧长度并不固定，但有两个限制：
     *
     * 1. 每个值不超过uint8_t 的范围；
     * 2. 加上固定的一个前缀，值的总数量不超过3，最多只能附加两个值；
     *
     * 所以接收和发送缓冲区至少要能容纳3 个值。这样规定是为了让任何从机和主机都有能力处理标准响应，无论它们具体是什么配置。
     *
     * 可以添加其他自定义前缀，扩展标准响应帧的种类；也可以用其他类型的自定义帧作为响应，
     * 但是自定义响应的帧类型必须和请求帧相同。
     *
     * ## 自定义帧类型
     *
     * 标准响应帧使用了 ! 作为帧类型标志，不建议在自定义帧中使用。
     * 除此之外，请求帧可以使用任何ASCII 码作为帧类型，建议使用可打印字符。
     *
     * ## 同步IO 逻辑
     *
     * 为了简化实现，整体采用同步IO 逻辑，不依赖回调，但仍然可以实现阻塞或无阻塞两种模式，
     * 方法就是在每个阻塞IO 前添加轮询标志，类似reactor 模式。
     *
     * 无论是主机还是从机，一次对话中总是包含三个阻塞点，只是顺序不同。对于主机：
     *
     * 1. 发送请求帧；
     * 2. 等待响应；
     * 3. 接收响应帧；
     *
     * 对于从机：
     *
     * 1. 等待请求帧；
     * 2. 接收请求帧；
     * 3. 发送响应帧；
     *
     * 这三步都会引起阻塞，但调用者可以轮询标志位，在不会阻塞的时间发起操作。
     *
     * ## 传输大量数据
     *
     * hexpro 协议并不打算被用于传输大量数据，主要是用来传输控制参数。
     * 如果有必要，可以用hexpro 作为控制信号，搭配另一个适合大量数据传输的协议。
     *
     * @tparam Child
     * @tparam SerialType
     */
    template <typename SerialType = decltype(Serial)>
    class ProtocolHandler {
       private:
        SerialType &_serial_instance;

        // uint32_t _read_timeout;
        // uint32_t _write_timeout;    //TODO: TimeType
        uint32_t _response_timeout = 0;
        uint32_t _response_waiting_start_time = 0;

        ValueType _tx_value[TX_VALUE_COUNT];
        ValueType _rx_value[RX_VALUE_COUNT];

        error_level _last_error = error_level::none;

        uint8_t _tx_value_count = 0;
        uint8_t _rx_value_count = 0;

        char _addr;
        char _tx_frame_type;
        char _rx_frame_type;

        // bool _is_master;

        /**
         * @brief 根据要发送的值的数量计算帧长度，这个计算结果是偏大的，因为值的高位0 会被忽略
         *
         * @param value_count
         * @return constexpr size_t
         */
        static constexpr size_t _calc_frame_len(auto value_count) {
            return MIN_FRAME_LEN + value_count * VALUE_WIDTH * 2;
        }

        /**
         * @brief 帧长度是否大于整个buffer 的尺寸
         *
         * @param n
         * @return true
         * @return false
         */
        static constexpr bool _is_buffer_size_too_small(auto n) {
            return SERIAL_TX_BUFFER_SIZE < n;
        }

        static constexpr bool _check_tx_buffer(auto buffer_available, auto frame_len) {
            if (_is_buffer_size_too_small(frame_len)) {
                return buffer_available >= (SERIAL_TX_BUFFER_SIZE - TX_BUFFER_THRESHOLD);
            }
            else {
                return (frame_len <= buffer_available) || ((frame_len - buffer_available) <= TX_BUFFER_THRESHOLD);
            }
        }

        static constexpr bool _check_tx_buffer_with_max_len(auto buffer_available) {
            auto frame_len = MAX_TX_FRAME_LEN;
            return (frame_len >= buffer_available) || ((frame_len - buffer_available) <= TX_BUFFER_THRESHOLD);
        }

        bool _check_tx_available() {
            size_t n = _serial_instance.availableForWrite();

            if constexpr (FIXED_TX_FRAME_LEN > 0) {
                return _check_tx_buffer(n, FIXED_TX_FRAME_LEN);
            }
            else {
                // 先用MAX_TX_FRAME_LEN 常数做比较，如果发送间隔比较大，可以省掉根据value_count 计算帧长度的计算量
                if (_check_tx_buffer_with_max_len(n)) {
                    return true;
                }
                else {
                    size_t frame_len = _calc_frame_len(_tx_value_count);
                    return _check_tx_buffer(n, frame_len);
                }
            }
        }

        /**
         * @brief 不管会不会阻塞，一股脑把整个帧发完
         *
         */
        bool _send_frame() {
            bool ok = 1;

            auto write = [this, &ok](uint8_t d) {
                ok &= _serial_instance.write(d);
            };

            auto write2 = [this, &ok](uint8_t d1, uint8_t d2) {
                ok &= _serial_instance.write(d1);
                ok &= _serial_instance.write(d2);
            };

            // 发送帧头
            write(_addr);
            write(_addr);
            write(_tx_frame_type);

            uint8_t sum = _tx_frame_type;  // 校验值

            // 发送数据，要将原始值转换成十六进制字符串，big-endian、msb first、右对齐、忽略高位0 字节
            // 同时计算校验值

            union {
                ValueType d;
                uint8_t bytes[VALUE_WIDTH];
            } data;

            for (uint8_t i = 0; i < _tx_value_count; ++i) {
                if (_tx_value[i] != 0) {
                    data.d = _tx_value[i];

                    for (auto j = static_cast<int_fast8_t>(VALUE_WIDTH - 1); j >= 0; --j) {
                        auto b = data.bytes[j];
                        if (b == 0) {
                            // 忽略高字节0
                            continue;
                        }

                        sum += b;
                        auto hex = hide::byte_to_hex(b);
                        write2(hex.h, hex.l);
                    }
                }

                write(',');
            }

            auto hex = hide::byte_to_hex(sum);
            write2(hex.h, hex.l);

            write2('\r', '\n');
            return ok;
        }

       public:
        ProtocolHandler(SerialType &s = Serial) :
            _serial_instance(s) {
            //
        }

        error_level last_error() const {
            return _last_error;
        }

        void end_session() {
            /*TODO:*/
            reset_tx_data();
        }

        void begin_session(uint8_t address = '#') {
            _addr = address;
        }

        void set_read_timeout(uint32_t t) {
            _serial_instance.setTimeout(t);
        }

        void set_response_timeout(uint32_t t) {
            _response_timeout = t;
        }

        void set_tx_data(uint8_t n, ValueType v) {
            if (_tx_value_count <= n) {
                _tx_value_count = n + 1;
            }
            _tx_value[n] = v;
        }

        ValueType tx_data(uint8_t n) const {
            return _tx_value[n];
        }

        ValueType rx_data(uint8_t n) const {
            return _rx_value[n];
        }

        uint8_t rx_data_count() const {
            return _rx_value_count;
        }

        uint8_t tx_data_count() const {
            return _tx_value_count;
        }

        void reset_tx_data() {
            _tx_value_count = 0;
        }

        void set_tx_frame_type(uint8_t t) {
            _tx_frame_type = t;
        }

        uint8_t tx_frame_type() const {
            return _tx_frame_type;
        }

        uint8_t rx_frame_type() const {
            return _rx_frame_type;
        }

        /**
         * @brief 检查当前是否可能无阻塞的发送帧
         *
         * 发送帧时，期望的工作方式是：如果不能发送，就原地阻塞，或通知上层调用者；一旦开始发送，就无阻塞的把整个帧发送完；
         *
         * 要求一次性发送完，原因是：
         *
         * 1. 可以简化代码逻辑，要是发送到一半退出，之后断点续传，整体逻辑会很麻烦；
         * 2. 某些串口协议有时序要求，比如Modbus RTU，如果一帧的中途间断，下次回来续传的时间是不确定的，可能导致传输异常；
         * 3. 串口接收者可能有超时设置，中途间断时间过长会触发超时；
         * 4. 一帧数据量通常比较小，可以全部塞进缓冲区里；
         * 5. 就算无法完整塞进缓冲区，如果波特率较高，阻塞的时间也很短；
         * 6. 如果要传输的数据量比较大，更好的方法是由上层应用代码把数据分成多个帧；
         *
         * 返回true 时，发送不一定不阻塞；返回false 时，发送不太可能不阻塞。
         *
         * @return true
         * @return false
         */
        bool tx_available() {
            return _check_tx_available();
        }

        /**
         * @brief 前一帧数据是否已发送完毕
         *
         * @return true
         * @return false
         */
        bool tx_complete() {
            return _serial_instance.availableForWrite() == SERIAL_TX_BUFFER_SIZE;
        }

        bool tx() {
            return _send_frame();
        }

        /**
         * @brief 检查当前是否有数据可读
         *
         * 如果检测到帧头的几个字节，就返回true，接下来可以调用rx 接收剩余部分。
         * 如果接收到了数据，但地址或格式不正确，则将这些数据从缓冲区删除，直到遇到帧头。
         *
         * 返回true 时，后续数据可能正在接收中，调用rx 中途还是会阻塞。
         *
         * @return true
         * @return false
         */
        bool rx_available() {
            auto n = _serial_instance.available();
            if (n > 5) {
                // 帧头包括两字节的地址和一字节帧类型，帧尾有一字节校验和两字节换行符，所以至少要有6 字节长度的数据，才值得处理
                for (; n > 0; --n) {
                    uint8_t d = static_cast<uint8_t>(_serial_instance.peek());
                    if (d == _addr) {
                        return true;  // 检测到了正确的地址，可以开始接收
                                      // 但也有可能是错误的，还需要在rx 函数中确定
                    }
                    else {
                        _serial_instance.read();  // 把一字节从缓冲区删除
                    }
                }
                // 读完了现有的数据，没检测到帧头
            }

            return false;

            // TODO: wait_for_rx
        }

        /**
         * @brief 开始读取数据
         *
         * 只有完整接收并解析了一帧数据后，才会返回true。之后可以通过rx_frame_type 获取接收到的帧类型，
         * 用rx_data<N> 获取接收到的值
         *
         * 返回true 时，仍然有可能发生了错误。比如接收到的值数量过多，但帧的格式以及校验和都正确，
         * 此时会将last_error 设置为too_many_value。
         *
         * @return true
         * @return false 发生了超时等错误，接受数据不完整
         */
        bool rx() {
            int ch;
            int double_ch[2] = {0};

            uint8_t stage = 0;
            uint8_t sum = 0;

            uint8_t value_num = 0;

            constexpr uint8_t VALUE_WIDTH_BIT = VALUE_WIDTH * 8;

            union {
                ValueType v;
                uint8_t b[VALUE_WIDTH * 8];
            } value = {0};

            uint8_t value_index = VALUE_WIDTH_BIT;
            uint8_t half_byte = 0;
            uint8_t frame_sum = 0;
            bool is_high_half = true;  // 正在读取的字符是否对应一字节的高4 位
            bool is_hex = false;

            _last_error = error_level::none;

            while (1) {
                ch = _serial_instance.read();
                if (ch == -1) {
                    _last_error = error_level::rx_timeout;
                    return false;
                }

                switch (stage) {
                    case 0:  // 帧同步,持续读取，直到找到帧头
                        double_ch[1] = ch;

                        // 检测到两字节地址，跳出循环，执行后续处理，否则继续读取，直到超时
                        if (double_ch[0] == _addr && double_ch[1] == _addr) {
                            ++stage;
                        }
                        else {
                            // 向后挪动一字节
                            double_ch[0] = double_ch[1];
                            // DEBUG
                            // _serial_instance.print("Stage - 0: ");
                            // _serial_instance.print(static_cast<char>(ch));
                            // _serial_instance.print("\r\n");
                        }
                        break;

                    case 1:  // 读取帧类型
                        sum += static_cast<uint8_t>(ch);
                        _rx_frame_type = sum;
                        ++stage;
                        break;

                    case 2:  // 读取数据
                        is_hex = true;
                        if (ch >= '0' && ch <= '9') {
                            // 数字解码
                            half_byte += static_cast<uint8_t>(ch - '0');
                        }
                        else if (ch >= 'A' && ch <= 'F') {
                            half_byte += static_cast<uint8_t>(ch - 'A' + 10);
                        }
                        else if (ch >= 'a' && ch <= 'f') {
                            half_byte += static_cast<uint8_t>(ch - 'a' + 10);
                        }
                        else {
                            is_hex = false;
                        }

                        if (is_hex) {
                            if (is_high_half) {
                                // 正在读取高四位，把刚才的转换结果挪到高位
                                half_byte <<= 4;
                                is_high_half = false;  // 接下来准备读取低四位
                            }
                            else {
                                // 读到低四位后，凑齐了高低四位
                                if (value_index == 0) {
                                    // 如果字节数超过了值类型的宽度，触发value_error
                                    _last_error = error_level::value_error;
                                    return false;
                                }
                                else {
                                    value_index -= 8;
                                    value.v += static_cast<ValueType>(frame_sum) << half_byte;
                                }
                                
                                is_high_half = true;
                                sum += half_byte;
                                frame_sum = half_byte;
                                half_byte = 0;
                            }
                        }
                        else if (!is_high_half) {
                            // 如果在读取低4 位字符时读到了16 进制字符以外的东西，触发编码错误，终止读取
                            _last_error = error_level::value_error;
                            return false;
                        }
                        else if (ch == '\r') {  // 似乎遇到了帧尾
                            if (value_index < (value_index - 8)) {
                                // 帧尾前的一个值应该是一字节校验码，
                                // 如果校验码不是一字节，返回编码错误
                                _last_error = error_level::value_error;
                                return false;
                            }
                            ++stage;
                            break;
                        }
                        else if (ch == ',') {  // 读到值分隔符
                            if (value_num >= RX_VALUE_COUNT) {
                                _last_error = error_level::too_many_value;
                                // too_many_value 不会终止读取
                            }
                            else {
                                // 默认从最高字节开始解析16 进制字符，
                                // 一个值读完后，因为格式是右对齐，必须右移。
                                // 比如：读取到aabb 时，value 里默认左对齐，值为aabb0000，
                                // 读到逗号以后，先要把value 右移16 位，变成右对齐形式0000aabb，
                                // 然后再存到缓冲区
                                _rx_value[value_num] = value.v >> value_index;
                                value.v = 0;
                                ++value_num;
                                value_index = VALUE_WIDTH_BIT;
                            }
                        }
                        else {
                            _last_error = error_level::value_error;
                            return false;
                        }

                        break;

                    case 3:  // 读取帧尾
                        // 如果帧尾不是\r\n 也视为错误
                        if (ch != '\n') {
                            _last_error = error_level::no_proper_end;
                            return false;
                        }
                        else {  // 否则获取上一个读到的值，也就是校验和，然后与计算的校验和比较
                            sum -= frame_sum;
                            if (sum != frame_sum) {
                                // DEBUG
                                // _serial_instance.print("Sum: ");
                                // _serial_instance.print(sum);
                                // _serial_instance.print(", Frame: ");
                                // _serial_instance.print(frame_sum);
                                // _serial_instance.print("\r\n");
                                _last_error = error_level::sum_mismatch;
                                return false;
                            }
                            else {
                                return true;
                            }
                        }
                }
            }
        }

        /**
         * @brief 在超时限度内，检查从机是否返回了响应
         *
         * 如果已经读到响应，返回error_level::none；
         * 如果已超时，返回error_level::response_timeout;
         * 如果尚未超时，也没有收到响应，返回error_level::waiting;
         *
         * @return error_level
         */
        error_level response_available() {
            if(_response_waiting_start_time == 0) {
                // 等待还没有开始，检查上一个请求帧有没有发完
                if(tx_complete()) {
                    _response_waiting_start_time = millis();
                }
            }
            else {
                if(rx_available()) {
                    // 似乎收到了从机响应
                    return error_level::none;
                }
                else if((millis() - _response_waiting_start_time) > _response_timeout) {
                    return error_level::response_timeout;
                }
                else {
                    return error_level::waiting;
                }
            }
        }
    };

}  // namespace hexpro